/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.task.zkex.list;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.entities.beans.table.TableField;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.messages.request.ListInitRequestMessage;
import cn.easyplatform.messages.request.ListQueryRequestMessage;
import cn.easyplatform.messages.request.ListSortRequestMessage;
import cn.easyplatform.messages.response.ListInitResponseMessage;
import cn.easyplatform.messages.response.ListPagingResponseMessage;
import cn.easyplatform.messages.response.ListQueryResponseMessage;
import cn.easyplatform.messages.vos.ListInitVo;
import cn.easyplatform.messages.vos.datalist.*;
import cn.easyplatform.spi.service.ListService;
import cn.easyplatform.type.*;
import cn.easyplatform.web.dialog.MessageBox;
import cn.easyplatform.web.exporter.excel.ExcelExporter;
import cn.easyplatform.web.exporter.pdf.PdfExporter;
import cn.easyplatform.web.ext.ComponentBuilder;
import cn.easyplatform.web.ext.zul.Datalist;
import cn.easyplatform.web.service.ServiceLocator;
import cn.easyplatform.web.task.BackendException;
import cn.easyplatform.web.task.MainTaskSupport;
import cn.easyplatform.web.task.OperableHandler;
import cn.easyplatform.web.task.event.EventListenerHandler;
import cn.easyplatform.web.task.zkex.ListSupport;
import cn.easyplatform.web.task.zkex.list.filter.DefaultFieldFilter;
import cn.easyplatform.web.task.zkex.list.panel.QueryPanelBuilder;
import cn.easyplatform.web.utils.ExtUtils;
import cn.easyplatform.web.utils.WebUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.util.media.AMedia;
import org.zkoss.util.resource.Labels;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.HtmlBasedComponent;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.SortEvent;
import org.zkoss.zk.ui.metainfo.EventHandler;
import org.zkoss.zk.ui.metainfo.EventHandlerMap;
import org.zkoss.zkmax.zul.Filedownload;
import org.zkoss.zul.*;
import org.zkoss.zul.impl.MeshElement;

import java.io.ByteArrayOutputStream;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public abstract class AbstractListBuilder implements ComponentBuilder,
        ListSupport {

    protected final static Logger log = LoggerFactory.getLogger(AbstractListBuilder.class);

    private static final Pattern pattern = Pattern.compile("go\\((.+),"
            + Constants.OPEN_EMBBED + "(.+?)");

    protected OperableHandler mainTaskHandler;

    private Datalist entity;

    protected ListVo layout;

    private PanelSupport ps;

    // 0:无;1:单击;2:双击
    protected int clickType = 0;

    // 记录索引，在表达式用$LIST_INDEX
    protected int listIndex;

    protected boolean hasTotal;

    protected String rowStyle;

    protected String headStyle;

    protected String footStyle;

    protected boolean useHeaderVariable;

    private Component anchor;

    public AbstractListBuilder(OperableHandler mainTaskHandler, Datalist entity, Component anchor) {
        this.mainTaskHandler = mainTaskHandler;
        this.entity = entity;
        this.anchor = anchor;
    }

    @Override
    public Component build() {
        ListInitVo iv = null;//new ListInitVo();
        if (anchor == null) {
            iv = new ListInitVo();
            iv.setHost(entity.getHost());
        } else {
            ListRowVo rowVo = WebUtils.getAnchorValue(anchor);
            ListSupport support = (ListSupport) mainTaskHandler;
            Object[] data = support.isCustom() ? rowVo.getData() : rowVo
                    .getKeys();
            if (support.getEntity().getType().equals(Constants.CATALOG) || support.getEntity().getType().equals(Constants.HIERARCHY))
                data = rowVo.getData();
            iv = new ListListInitVo(support.getComponent().getId(), data);
        }
        iv.setEntityId(entity.getEntity());
        iv.setId(entity.getId());
        iv.setBefore(entity.getBefore());
        iv.setAfter(entity.getAfter());
        iv.setImmediate(entity.isImmediate());
        iv.setInit(entity.getInit());
        if (!entity.getType().equals(Constants.DETAIL) && !entity.isFetchAll())
            iv.setPageSize(entity.getPageSize());
        iv.setEditable(isEditable());
        iv.setType(entity.getType());
        iv.setGroup(entity.getGroup());
        if (!Strings.isBlank(entity.getLevelBy())
                && Strings.isBlank(entity.getOrderBy())) {
            String[] args = entity.getLevelBy().split(",");
            if (args.length != 2)
                throw new EasyPlatformWithLabelKeyException(
                        "datalist.level.error", entity.getId(), layout.getErrMsg());
            entity.setOrderBy(args[1] + "," + args[0]);
        }
        iv.setOrderBy(entity.getOrderBy());
        iv.setCondition(entity.getCondition());
        iv.setUseQueryResult(entity.isUseQueryResult());
        iv.setLock(entity.isLock());
        iv.setFilter(entity.getFilter());
        iv.setCache(entity.isCache());
        iv.setShowPanel(entity.isShowPanel());
        return init(iv);
    }

    protected Component init(ListInitVo iv) {
        ListService dls = ServiceLocator
                .lookup(ListService.class);
        IResponseMessage<?> resp = dls.doInit(new ListInitRequestMessage(
                mainTaskHandler.getId(), iv));
        if (!resp.isSuccess())
            throw new BackendException(resp);
        layout = ((ListInitResponseMessage) resp).getBody();
        boolean isVisible = true;
        if (mainTaskHandler instanceof MainTaskSupport)
            isVisible = ((MainTaskSupport) mainTaskHandler).isVisible();
        if (isVisible) {
            if (layout.getErrMsg() != null)
                setEmptyMessage(layout.getErrMsg());
            rowStyle = Strings.isBlank(entity.getRowStyle()) ? layout.getRowStyle()
                    : entity.getRowStyle();
            headStyle = Strings.isBlank(entity.getHeadStyle()) ? layout
                    .getHeadStyle() : entity.getHeadStyle();
            footStyle = Strings.isBlank(entity.getHeadStyle()) ? layout
                    .getFootStyle() : entity.getFootStyle();
            // 判断是否单击事件
            if (!Strings.isBlank(entity.getEvent())) {
                clickType = 2;
                if (entity.getEvent().startsWith("onClick:")) {
                    clickType = 1;
                    entity.setEvent(StringUtils.substringAfter(
                            entity.getEvent(), "onClick:"));
                } else if (entity.getEvent().startsWith("onDoubleClick:")) {
                    entity.setEvent(StringUtils.substringAfter(
                            entity.getEvent(), "onDoubleClick:"));
                } else if ((!Strings.isBlank(entity.getPageId())
                        && entity.getOpenModel() == Constants.OPEN_EMBBED
                        && !Strings.isBlank(entity.getContainer()) && entity
                        .getEvent().indexOf("open") >= 0)// Open的方法
                        || pattern.matcher(entity.getEvent()).find()// go的方法
                        || entity.getEvent().indexOf(".reload()") > 0
                        || entity.isDurable())// 子列表reload的方法
                    clickType = 1;
            } else {
                EventHandlerMap em = entity.getEventHandlerMap();
                if (em != null && !em.isEmpty()) {
                    em.getEventNames().forEach(name -> {
                        List<EventHandler> evts = em.getAll(name);
                        evts.forEach(eh -> {
                            if (eh.isEffective(entity)) {
                                if (name.equals(Events.ON_CLICK)) {
                                    clickType = 1;
                                    getComponent().setEvent(eh.getZScript().getRawContent());
                                } else if (name.equals(Events.ON_DOUBLE_CLICK)) {
                                    clickType = 2;
                                    getComponent().setEvent(eh.getZScript().getRawContent());
                                } else {//其它事件
                                    getComponent().addEventListener(name, new EventListenerHandler(name,
                                            this, eh.getZScript().getRawContent(), anchor));
                                }
                            }
                        });
                    });
                }
            }
            Component c = createContent();
            buildBody();
            layout.setData(null);
            return c;
        } else {
            if (layout.getErrMsg() != null)
                throw new EasyPlatformWithLabelKeyException(
                        "component.init.error", entity.getId(), layout.getErrMsg());
            return null;
        }
    }

    protected void buildBody() {
        //如果要显示行号，添加一列
        if (entity.isShowRowNumbers()) {
            layout.setHeaders(new ArrayList<>(layout.getHeaders()));
            ListHeaderVo header = new ListHeaderVo();
            header.setName("LIST_INDEX");
            header.setWidth("80px");
            header.setType(FieldType.INT);
            header.setVisible(true);
            header.setAlign("left");
            header.setTitle(Labels.getLabel("datalist.row.no"));
            layout.getHeaders().add(0, header);
        }
        hasTotal = !Strings.isBlank(layout.getOnFooter());
        buildAuxHead();
        createHeader();
        redraw(layout.getData());
        refreshFoot();
    }

    protected void buildAuxHead() {
        if (layout.getAuxHeads() != null
                && !layout.getAuxHeads().isEmpty()) {
            boolean rowspan = false;
            for (ListAuxHeadVo ah : layout.getAuxHeads()) {
                Auxhead auxHead = new Auxhead();
                auxHead.setParent(getComponent());
                auxHead.setStyle(Strings.isBlank(ah.getStyle()) ? entity
                        .getAuxHeadStyle() : ah.getStyle());
                if (entity.isShowRowNumbers() && !rowspan) {
                    Auxheader auxHeader = new Auxheader();
                    auxHeader.setRowspan(layout.getAuxHeads().size());
                    //auxHeader.setLabel(Labels.getLabel("datalist.row.no"));
                    auxHead.appendChild(auxHeader);
                    rowspan = true;
                }
                for (ListAuxHeaderVo ahv : ah.getAuxHeaders()) {
                    Auxheader auxHeader = new Auxheader();
                    auxHeader.setLabel(ahv.getTitle());
                    if (!Strings.isBlank(ahv.getStyle()))
                        auxHeader.setStyle(ahv.getStyle());
                    if (!Strings.isBlank(ahv.getAlign()))
                        auxHeader.setAlign(ahv.getAlign());
                    if (!Strings.isBlank(ahv.getValign()))
                        auxHeader.setValign(ahv.getValign());
                    if (!Strings.isBlank(ahv.getIconSclass()))
                        auxHeader.setIconSclass(ahv.getIconSclass());
                    if (!Strings.isBlank(ahv.getImage()))
                        auxHeader.setImage(ahv.getImage());
                    if (!Strings.isBlank(ahv.getHoverimg()))
                        auxHeader.setHoverImage(ahv.getHoverimg());
                    if (ahv.getRowspan() > 0)
                        auxHeader.setRowspan(ahv.getRowspan());
                    if (ahv.getColspan() > 0)
                        auxHeader.setColspan(ahv.getColspan());
                    auxHead.appendChild(auxHeader);
                }
            }
        }
    }

    protected Component buildPanel(String xml,
                                   Collection<ListQueryParameterVo> pvs) {
        ps = new QueryPanelBuilder(this);
        return ps.build(xml, pvs);
    }

    @Override
    public void query(Map<String, ListQueryParameterVo> params) {
        if (params == null)
            params = ps.getQueryValue();
        for (ListQueryParameterVo qpv : params.values()) {
            if (qpv.getOp() != null && "between".equals(qpv.getOp())
                    || "not between".equals(qpv.getOp())) {
                if (qpv.getValues().size() != 2) {
                    MessageBox.showMessage(Labels
                            .getLabel("message.dialog.title.error"), Labels
                            .getLabel("datalist.parameter.error",
                                    new Object[]{qpv.getName()}));
                    return;
                }
            }
        }
        // 为了保证Session可以Serialize到硬盘上，必须用new
        // ArrayList<ListQueryParameterVo>(map.values())
        ListQueryVo qv = new ListQueryVo(entity.getId(),
                new ArrayList<ListQueryParameterVo>(params.values()));
        ListService dls = ServiceLocator
                .lookup(ListService.class);
        IResponseMessage<?> resp = dls.doQuery(new ListQueryRequestMessage(
                mainTaskHandler.getId(), qv));
        if (resp.isSuccess()) {
            ListQueryResultVo result = ((ListQueryResponseMessage) resp)
                    .getBody();
            if (result.getData() == null || result.getData().isEmpty())
                setPagingInfo(0, 0);
            else
                setPagingInfo(result.getTotalSize(), 0);
            clear();
            listIndex = 0;
            redraw(result.getData());
            refreshFoot();
        } else
            throw new BackendException(resp);
    }

    @Override
    public void export(String type, Object... exportHeaders) {
        export(getComponent(), type, exportHeaders);
    }

    @Override
    public void exportAll(String type, Object... exportHeaders) {
        export(getComponent(), type, exportHeaders);
    }

    /**
     * 导出指定的组件(tree、listbox、grid)内容
     *
     * @param mesh
     * @param type
     * @param exportHeaders
     */
    protected void export(MeshElement mesh, String type, Object... exportHeaders) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            List<ListHeaderVo> headers = getHeaders();
            if (exportHeaders.length == 0) {
                //默认不显示的就不导出
                for (ListHeaderVo hv : headers)
                    hv.setExport(hv.isVisible());
            } else {
                for (ListHeaderVo hv : headers)
                    hv.setExport(false);
                if (exportHeaders[0] instanceof Number) {
                    List<ListHeaderVo> temp = new ArrayList<>();
                    for (ListHeaderVo hv : headers) {
                        if (hv.isVisible()) {
                            temp.add(hv);
                        }
                    }
                    for (int i = 0; i < exportHeaders.length; i++) {
                        int idx = ((Number) exportHeaders[i]).intValue();
                        if (idx < temp.size())
                            temp.get(idx).setExport(true);
                    }
                } else if (exportHeaders[0] instanceof String) {
                    for (Object name : exportHeaders) {
                        for (ListHeaderVo hv : headers) {
                            if (name.equals(hv.getName())) {
                                hv.setExport(true);
                                break;
                            }
                        }
                    }
                }
            }
            if (type.equalsIgnoreCase("pdf")) {
                PdfExporter exporter = new PdfExporter();
                exporter.export(layout, mesh, out);
                AMedia amedia = new AMedia(getName() + ".pdf", "pdf",
                        "application/pdf", out.toByteArray());
                Filedownload.save(amedia);
            } else {
                ExcelExporter exporter = new ExcelExporter();
                exporter.export(layout, mesh, out);
                AMedia amedia = new AMedia(getName() + ".xlsx", "xlsx",
                        "application/file", out.toByteArray());
                Filedownload.save(amedia);
            }
        } catch (Exception ex) {
            if (log.isErrorEnabled())
                log.error("export {},{}", getName(), ex);
            throw new RuntimeException(ex);
        } finally {
            IOUtils.closeQuietly(out);
        }
    }

    /**
     * @param rv
     * @return
     */
    protected FieldVo[] castTo(ListRowVo rv) {
        FieldVo[] fields = new FieldVo[rv.getData().length];
        for (int i = 0; i < fields.length; i++) {
            ListHeaderVo header = layout.getHeaders().get(i);
            if (header.getType() != null) {
                fields[i] = new FieldVo(header.getType());
                fields[i].setValue(rv.getData()[i]);
                fields[i].setName(header.getName());
            }
        }
        return fields;
    }

    @Override
    public void filter(ListHeaderVo header) {
        new DefaultFieldFilter().doFilter(header, this);
    }

    @Override
    public Map<String, Component> getManagedPanelComponents() {
        if (ps == null)
            return Collections.emptyMap();
        return ps.getManagedComponents();
    }

    @Override
    public void sort(String name, boolean isAscending) {
        Component grid = getComponent();
        Component head = null;
        if (grid instanceof Listbox) {
            List<Listheader> headers = ((Listbox) grid)
                    .getListhead().getChildren();
            for (Listheader header : headers) {
                ListHeaderVo hv = header.getValue();
                if (hv != null && hv.getName().equals(name)) {
                    head = header;
                    break;
                }
            }
        } else if (grid instanceof Tree) {
            List<Treecol> headers = ((Tree) grid)
                    .getTreecols().getChildren();
            for (Treecol header : headers) {
                ListHeaderVo hv = (ListHeaderVo) header
                        .getAttribute("value");
                if (hv != null && hv.getName().equals(name)) {
                    head = header;
                }
            }
        } else if (grid instanceof Grid) {
            List<Column> headers = ((Grid) grid).getColumns().getChildren();
            for (Column header : headers) {
                ListHeaderVo hv = header.getValue();
                if (hv != null && hv.getName().equals(name)) {
                    head = header;
                    break;
                }
            }
        }
        if (head != null)
            doSort(head, isAscending);
    }

    protected void doSort(Component header, boolean isAscending) {
        if (getEntity().getType().equals(Constants.DETAIL)) {
            SortEvent evt = new SortEvent(Events.ON_SORT, header, !isAscending);
            Events.sendEvent(evt);
        } else {
            ListHeaderVo hv = null;
            if (header instanceof Listheader)
                hv = ((Listheader) header).getValue();
            else if (header instanceof Column)
                hv = ((Column) header).getValue();
            else
                hv = (ListHeaderVo) header.getAttribute("value");
            ListService dls = ServiceLocator
                    .lookup(ListService.class);
            IResponseMessage<?> resp = dls.doSort(new ListSortRequestMessage(
                    mainTaskHandler.getId(), new ListSortVo(getComponent().getId(), hv
                    .getField(), isAscending)));
            if (!resp.isSuccess()) {
                MessageBox.showMessage(resp);
            } else {
                ListPagingResponseMessage lp = (ListPagingResponseMessage) resp;
                List<ListRowVo> rowset = lp.getBody();
                clear();
                listIndex = 0;
                setPagingInfo(-1, 0);
                redraw(rowset);
            }
        }
    }

    @Override
    public boolean isEditable() {
        return false;
    }

    @Override
    public String getName() {
        return layout.getName();
    }

    @Override
    public boolean isCustom() {
        return layout.isCustom();
    }

    @Override
    public List<ListHeaderVo> getHeaders() {
        return layout.getHeaders();
    }

    @Override
    public ListVo getLayout() {
        return layout;
    }

    @Override
    public OperableHandler getMainHandler() {
        return mainTaskHandler;
    }

    public Datalist getEntity() {
        return entity;
    }

    @Override
    public void print() {
        ExtUtils.print(getComponent(), null, null);
    }

    @Override
    public Component getAnchor() {
        return anchor;
    }

    protected abstract void setEmptyMessage(String msg);

    protected abstract void redraw(List<ListRowVo> rowset);

    protected abstract void createHeader();

    protected abstract void addEventListener(Component comp, String evtName);

    protected abstract Component createContent();

    protected abstract void setPagingInfo(int totalSize, int activePageNo);

    protected abstract void setMenupopup(HtmlBasedComponent head);
}
